using System;
using System.Collections.Generic;
using System.Text;
using System.Net.NetworkInformation;
using System.Xml;
using System.IO;
using System.Reflection;
using System.Diagnostics;
using System.Timers;
using System.Collections.ObjectModel;

namespace NAst.Common
{
    #region for events
    public class NAAConfigurationChangeEventArgs : EventArgs
    {
        private NetworkConfiguration _config;

        public NetworkConfiguration ActiveConfiguration
        {
            get { return _config; }
            set { _config = value; }
        }

        internal NAAConfigurationChangeEventArgs(NetworkConfiguration activeConfiguration)
        {
            _config = activeConfiguration;
        }
    }

    public delegate void NAAConfigurationChangeEventHandler(object sender, NAAConfigurationChangeEventArgs e);


    public class NAAExceptionEventArgs : EventArgs
    {
        private List<Exception> _exceptions;

        public List<Exception> Exceptions
        {
            get { return _exceptions; }
            set { _exceptions = value; }
        }

        private string _errorTitle;

        public string ErrorTitle
        {
            get { return _errorTitle; }
            set { _errorTitle = value; }
        }


        internal NAAExceptionEventArgs(string errorTitle, List<Exception> exceptions)
        {
            _errorTitle = errorTitle;
            _exceptions = exceptions;
        }
    }

    public delegate void NAAExceptionEventHandler(object sender, NAAExceptionEventArgs e);
    #endregion

    /// <summary>
    /// Main class for the app
    /// </summary>
    public static class NetworkAwareAssistant
    {
        private static List<NetworkConfiguration> _confs = new List<NetworkConfiguration>();
        private static Timer _timerUpdate = new Timer(1000);

        public static ReadOnlyCollection<NetworkConfiguration> Configurations
        {
            get
            {
                return _confs.AsReadOnly();
            }
        }

        static NetworkAwareAssistant()
        {
            _timerUpdate.Elapsed += new ElapsedEventHandler(_timerUpdate_Elapsed);
            _timerUpdate.AutoReset = false;
        }

        /// <summary>
        /// Initialize the software
        /// </summary>
        public static void Initialize()
        {
            _confs.Clear();

            XmlDocument doc = LoadConfig();

            LoadConfig(doc);

            NetworkChange.NetworkAddressChanged += new NetworkAddressChangedEventHandler(NetworkChange_NetworkAddressChanged);

            Change();
        }

        public static void ReInitialize()
        {
            XmlDocument doc = LoadConfig();

            LoadConfig(doc);

            Change();
        }

        #region Events
        
        public static event NAAExceptionEventHandler ExceptionsRaised;
        private static void OnExceptionsRaised(string errorTitle, Exception exception)
        {
            OnExceptionsRaised(errorTitle, new List<Exception>(new Exception[] { exception }));
        }
        private static void OnExceptionsRaised(string errorTitle, Exception[] exceptions)
        {
            OnExceptionsRaised(errorTitle, new List<Exception>(exceptions));
        }
        private static void OnExceptionsRaised(string errorTitle, List<Exception> exceptions)
        {
            NAAExceptionEventHandler evt = ExceptionsRaised;
            if (evt != null)
                evt(null, new NAAExceptionEventArgs(errorTitle, exceptions));
        }

        public static event EventHandler ConfigurationLoaded;
        private static void OnConfigurationLoaded()
        {
            EventHandler evt = ConfigurationLoaded;
            if (evt != null)
                evt(null, EventArgs.Empty);
        }

        public static event NAAConfigurationChangeEventHandler ConfigurationChanged;
        private static void OnConfigurationChanged(NetworkConfiguration newConfig)
        {
            NAAConfigurationChangeEventHandler evt = ConfigurationChanged;
            if (evt != null)
                evt(null, new NAAConfigurationChangeEventArgs(newConfig));
        }

        public static event EventHandler NetworkAddressChanged;
        private static void OnNetworkAddressChanged()
        {
            EventHandler evt = NetworkAddressChanged;
            if (evt != null)
                evt(null, EventArgs.Empty);
        }

        #endregion

        #region Configuration loading
        /// <summary>
        /// Load the config file
        /// </summary>
        /// <returns></returns>
        private static XmlDocument LoadConfig()
        {
            string config = FindConfigDocument();
            if (string.IsNullOrEmpty(config))
                throw new InvalidOperationException("Please configure NetworkAwareAssistant");
            XmlDocument doc = new XmlDocument();
            doc.Load(config);
            return doc;
        }

        /// <summary>
        /// Instantiate the netlets & tests
        /// </summary>
        /// <param name="doc"></param>
        private static void LoadConfig(XmlDocument doc)
        {
            Dictionary<string, Type> allNetletsType = FindNetletsType(doc);
            Dictionary<string, Type> allTestsType = FindTestsType(doc);

            LoadState(allNetletsType);

            foreach (XmlElement elm in doc.SelectNodes("/Config/NetworkConfiguration"))
            {
                NetworkConfiguration cfg = new NetworkConfiguration(elm.GetAttribute("name"));
                cfg.Initialize(elm, allNetletsType, allTestsType);
                _confs.Add(cfg);
            }

            OnConfigurationLoaded();
        }

        private static Dictionary<string, Type> FindTestsType(XmlDocument doc)
        {
            Dictionary<string, Type> ret = new Dictionary<string, Type>();
            ret.Add("IsConnected", typeof(IsConnectedTester));
            ret.Add("DhcpServer", typeof(DhcpServerTester));
            ret.Add("DnsServer", typeof(DnsServerTester));
            ret.Add("DnsSuffix", typeof(DnsSuffixTester));
            return ret;
        }


        private static Dictionary<string, Type> FindNetletsType(XmlDocument doc)
        {
            Dictionary<string, Type> ret = new Dictionary<string, Type>();

            List<Exception> exceptions = new List<Exception>();

            foreach (XmlElement elm in doc.SelectNodes("/Config/Assemblies/Add"))
            {
                string name = elm.GetAttribute("name");
                if (string.IsNullOrEmpty(name))
                    continue;
                try
                {
                    Assembly.Load(name);
                }
                catch (Exception ex)
                {
                    Trace.TraceError("Unable to load assembly " + name + " : " + ex.Message);
                    System.Diagnostics.EventLog.WriteEntry("NetworkAsssitant", "Unable to load assembly " + name + " : " + ex.Message, System.Diagnostics.EventLogEntryType.Warning);
                    exceptions.Add(ex);
                }
            }

            Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
            foreach (Assembly asm in assemblies)
            {
                foreach (Type t in asm.GetTypes())
                {
                    object[] attributes = t.GetCustomAttributes(typeof(NetletAttribute), false);
                    if (attributes != null && attributes.Length == 1)
                    {
                        NetletAttribute att = attributes[0] as NetletAttribute;
                        ret.Add(att.Name, t);
                    }
                }
            }

            if(exceptions.Count>0)
                OnExceptionsRaised("Some assemblies/netlets can't be found", exceptions);
            

            return ret;
        }

        public static string FindConfigDocument()
        {
            string inAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
            inAppData = Path.Combine(inAppData, "NetworkAwareAssistant\\NetworkAwareAssistant.xml");

            if (File.Exists(inAppData))
                return inAppData;

            inAppData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
            inAppData = Path.Combine(inAppData, "NetworkAwareAssistant\\NetworkAwareAssistant.xml");

            if (File.Exists(inAppData))
                return inAppData;


            if (File.Exists("NetworkAwareAssistant.xml"))
                return "NetworkAwareAssistant.xml";

            return null;
        }
        #endregion

        #region change handling

        /// <summary>
        /// Waits 1 second and then handles a network change
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void NetworkChange_NetworkAddressChanged(object sender, EventArgs e)
        {
            OnNetworkAddressChanged();
            if (_timerUpdate.Enabled == false)
                _timerUpdate.Start();
        }
        static void _timerUpdate_Elapsed(object sender, ElapsedEventArgs e)
        {
            Change();
        }

        /// <summary>
        /// Reload a previously saved state from
        /// disk
        /// </summary>
        /// <param name="allNetletsType"></param>
        private static void LoadState(Dictionary<string, Type> allNetletsType)
        {
            string saveFileName = GetSaveFileName();
            if (!File.Exists(saveFileName)) return;
            XmlDocument doc = new XmlDocument();
            doc.Load(saveFileName);

            // sets the currentConfig to the one loaded from
            // the file. 
            _currentConfig = NetworkConfiguration.Load(doc.DocumentElement, allNetletsType);
        }

        /// <summary>
        /// Save current active configuration to disk
        /// </summary>
        private static void SaveState()
        {
            XmlDocument doc = new XmlDocument();
            string saveFileName = GetSaveFileName();
            XmlElement currState = doc.CreateElement("CurrentState");
            doc.AppendChild(currState);
            if (_currentConfig != null)
            {
                _currentConfig.Save(currState);
            }
            doc.Save(saveFileName);
        }

        private static string GetSaveFileName()
        {
            String inAppData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
            if(Directory.Exists(Path.Combine(inAppData, "NetworkAwareAssistant")))
                return Path.Combine(inAppData, "NetworkAwareAssistant\\CurrentState.dat");

            return "CurrentState.dat";
        }

        private static NetworkConfiguration _currentConfig = null;
        
        /// <summary>
        /// Find the NetworkConfiguration that should be made
        /// active and activate it
        /// </summary>
        public static void Change()
        {
            bool bFound = false;
            try
            {
                foreach (NetworkConfiguration cfg in _confs)
                {
                    if (cfg.Test())
                    {
                        Change(cfg);
                        bFound = true;
                        break;
                    }
                }

                if (!bFound)
                {
                    Change(null);
                }
            }
            catch (Exception e)
            {
                OnExceptionsRaised("Errors while changing configuration", e);
            }
            SaveState();
        }

        /// <summary>
        /// Change the active configuration
        /// </summary>
        /// <param name="cfg">The configuration to activate</param>
        public static void Change(NetworkConfiguration cfg)
        {
            List<Exception> excLst = new List<Exception>();

            try
            {
                if (_currentConfig != null)
                {
                    List<Exception> exDisonnect = _currentConfig.Disconnect();
                    if (exDisonnect.Count > 0)
                        excLst.AddRange(exDisonnect);
                    _currentConfig = null;
                }

                if (_currentConfig != cfg)
                {
                    _currentConfig = cfg;
                    if (cfg != null)
                    {
                        List<Exception> exConnect = _currentConfig.Connect();
                        if (exConnect.Count > 0)
                            excLst.AddRange(exConnect);
                    }
                }
            }
            catch (Exception ex)
            {
                excLst.Add(ex);
            }
            if (excLst.Count > 0)
                OnExceptionsRaised("Errors while changing configuration", excLst);
            OnConfigurationChanged(_currentConfig);
        }

        #endregion

        public static void Terminate()
        {
            _confs.Clear();
        }
    }
}
